﻿#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include "HuffmanTree.hpp"

using std::string;
using std::vector;

//维护一个结构体,用来表示一个字节出现的次数和最后对应的Huffman编码
struct ByteInfo {
	unsigned char _ch;
	size_t _chCount;
	string _chCode;

	ByteInfo(const size_t& chCount = 0, const unsigned char ch = unsigned char())
		:_chCount(chCount)
		,_ch(ch)
	{}

	ByteInfo operator+(const ByteInfo& other) {
		return _chCount + other._chCount;
	}

	bool operator>(const ByteInfo& other) const {
		return _chCount > other._chCount;
	}

	bool operator<(const ByteInfo& other) {
		return _chCount < other._chCount;
	}

	bool operator!=(const ByteInfo& other) {
		return _chCount != other._chCount;
	}

	bool operator==(const ByteInfo& other) {
		return _chCount == other._chCount;
	}
};

class HuffmanFileCompressd {
public:
	HuffmanFileCompressd() {
		_fileInfo.resize(256);
		for (int i = 0; i < 256; i++) {
			_fileInfo[i]._ch = i;
		}
	}
	void clear();  //清除前一次操作留下的问题;
	void CompressFile(const string& filePath); //压缩文件函数
	void UnCompressFile(const string& filePath);  //解压缩


private:
	void getChCode(HuffmanTreeNode<ByteInfo>* root);  //获取字符对应编码
	string getFileName(const string& filePath);  //获取文件名
	string getFileSuffix(const string& filePath);  //获取文件后缀
	string GetLine(FILE* fin);
	
	//维护一个vector变量,进行对文件进行读取,然后统计次数
	vector<ByteInfo> _fileInfo;
};

void HuffmanFileCompressd::CompressFile(const string& filePath) {
	//Huffman编码压缩过程
	//1.统计源文件中每个字节出现的次数
	//2.根据次数创建Huffman树
	//3.获取每个字节的Huffman编码
	//4.创建文件,对文件进行一定的改写,在文件开始位置放置被压缩文件的后缀
	//以及字符和字符对应的出现次数,定为一个字符占一行,及每个字符次数后面跟一个\n;
	//5.用得到的编码对每个字节进行替换
	//1.
	FILE* fin = fopen(filePath.c_str(), "rb");
	if (fin == nullptr) {
		std::cout << "读取文件失败!" << std::endl;
		return;
	}
	while (1) {
		unsigned char buf[1024];
		size_t size = fread(buf, 1, 1024, fin);
		if (size == 0) {
			break;
		}
		for (int i = 0; i < size; i++) {
			_fileInfo[buf[i]]._chCount++;
		}
	}
	//2
	HuffmanTree<ByteInfo> ht(_fileInfo);

	//3.
	getChCode(ht.getRoot());

	//4.
	string FN = getFileName(filePath) + ".hz";
	string FNS = getFileSuffix(filePath) + '\n';
	FILE* fout = fopen(FN.c_str(), "wb");
	fputs(FNS.c_str(), fout);
	string s;
	int count = 0;
	for (int i = 0; i < 256; i++) {
		if (_fileInfo[i]._chCount != 0) {
			unsigned char c = _fileInfo[i]._ch;
			s += c;
			s += ":";
			s += std::to_string(_fileInfo[i]._chCount);
			s += '\n';
			count++;
		}
	}
	string ct = std::to_string(count) + '\n';
	fputs(ct.c_str(), fout);
	fputs(s.c_str(), fout);


	//5.
	fseek(fin, 0, SEEK_SET);
	unsigned char bits = 0;
	int bitsCount = 0;
	while (1) {
		unsigned char cha[1024];
		size_t size = fread(cha, 1, 1024, fin);
		if (size == 0) {
			break;
		}
		
		for (int i = 0; i < size; i++) {
			string& chCode = _fileInfo[cha[i]]._chCode;
			for (size_t k = 0; k < chCode.size(); k++) {
				bits <<= 1;
				if (chCode[k] == '1') {
					bits |= 1;
				}
				bitsCount++;
				if (bitsCount >= 8) {
					fputc(bits, fout);
					bits = 0;
					bitsCount = 0;
				}
			}
		}
		
	}
	if (bitsCount > 0 && bitsCount < 8) {
		bits <<= (8 - bitsCount);
		fputc(bits, fout);
	}
	fclose(fin);
	fclose(fout);
}

void HuffmanFileCompressd::UnCompressFile(const string& filePath) {
	//解压缩步骤
	//1.根据压缩文件内容,创建解压缩后的文件
	//2.创建Huffman树
	//3.根据压缩文件内容与Huffman树往新文件中还原源文件的东西

	//1.
	FILE* fin = fopen(filePath.c_str(), "rb");
	if (fin == nullptr) {
		std::cout << "解压失败:可能是路径错误!" << std::endl;
		return;
	}
	string&& s = GetLine(fin);
	string name = getFileName(filePath);
	name += "hz.";
	name += s;
	FILE* fout = fopen(name.c_str(), "wb");

	//2.
	string ct = GetLine(fin);
	int count = std::stoi(ct);

	for (int i = 0; i < count; i++) {
		
		string s = GetLine(fin);
		
		if (s == "") {
			s = '\n';
			s += GetLine(fin);
		}
		unsigned char dd = s[0];
		string t = s.substr(2);
		_fileInfo[dd]._chCount = std::stoi(s.substr(2));
	}
	HuffmanTree<ByteInfo> ht(_fileInfo);

	//3.
	count = 0;
	unsigned char buf[1024];
	auto cur = ht.getRoot();
	while (1) {
		
		size_t size = fread(buf, 1, 1024, fin);
		if (size == 0) {
			break;
		}
		
		
		for (size_t i = 0; i < size; i++) {
			unsigned char ch = buf[i];
			
			for (int k = 0; k < 8; k++) {
				if (ch & 0x80) {
					cur = cur->_right;
				}
				else {
					cur = cur->_left;
				}
				ch <<= 1;
				if (cur->_left == nullptr && cur->_right == nullptr) {
					fputc(cur->_weight._ch, fout);
					count++;
					cur = ht.getRoot();
					if (count == cur->_weight._chCount) {
						break;
					}
				}
			}
			if (count == (ht.getRoot()->_weight._chCount)) {
				break;
			}
		}
		if (count == (ht.getRoot()->_weight._chCount)) {
			break;
		}
	}
	fclose(fin);
	fclose(fout);
}

void HuffmanFileCompressd::getChCode(HuffmanTreeNode<ByteInfo>* root) {
	if (root == nullptr) {
		return;
	}
	getChCode(root->_left);
	getChCode(root->_right);
	if (root->_left == nullptr && root->_right == nullptr) {
		//逆推得出路径
		HuffmanTreeNode<ByteInfo>* cur = root;
		HuffmanTreeNode<ByteInfo>* parent = root->_parent;
		string& chCode = _fileInfo[root->_weight._ch]._chCode;
		while (parent != nullptr) {
			if (cur == parent->_left) {
				chCode += "0";
			}
			if (cur == parent->_right) {
				chCode += "1";
			}
			cur = parent;
			parent = parent->_parent;
		}
		std::reverse(chCode.begin(), chCode.end());
	}
}

string HuffmanFileCompressd::getFileName(const string& filePath) {
	if (filePath.rfind('\\') == std::string::npos) {
		return filePath.substr(0, filePath.rfind('.'));
	}
	size_t index = filePath.rfind('\\') + 1;
		
	return filePath.substr(index, filePath.rfind('.') - index);
}

string HuffmanFileCompressd::getFileSuffix(const string& filePath) {
	return filePath.substr(filePath.rfind('.') + 1);
}

string HuffmanFileCompressd::GetLine(FILE* fin) {
	string result;
	while (!feof(fin)) {
		unsigned char ch = fgetc(fin);
		if (ch == '\n') {
			break;
		}
		result += ch;
	}
	return result;
}

void HuffmanFileCompressd::clear() {
	for (int i = 0; i < 256; i++) {
		_fileInfo[i]._chCount = 0;
		_fileInfo[i]._chCode = "";
	}
}
